package nachos.userprog;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Stack;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;

/**
 * A kernel that can support multiple user processes.
 */
public class UserKernel extends ThreadedKernel {
    /**
     * Allocate a new user kernel.
     */
	
    public UserKernel() {
	super();
		
	
	
    }

    /**
     * Initialize this kernel. Creates a synchronized console and sets the
     * processor's exception handler.
     */
    public void initialize(String[] args) {
   	super.initialize(args);
	/**
	 * Group 7 start
	 */
	lmutex =  new Semaphore(1);
	tmutex = new Semaphore(1);
	freeMemory = new Stack<Integer>();
	processTableMap = new HashMap<Integer, TranslationEntry[]>();
	
	int numPhysPages = Machine.processor().getNumPhysPages();
	lmutex.P();
	for(int i = 0; i < numPhysPages; i++)
		freeMemory.add(i);
	lmutex.V();
	
	/**
	 * Group 7 end
	 */

	console = new SynchConsole(Machine.console());
	
	Machine.processor().setExceptionHandler(new Runnable() {
		public void run() { exceptionHandler(); }
	    });
    }

    /**
     * Test the console device.
     */	
    public void selfTest() {
	super.selfTest();

	System.out.println("Testing the console device. Typed characters");
	System.out.println("will be echoed until q is typed.");

	char c;

	do {
	    c = (char) console.readByte(true);
	    console.writeByte(c);
	}
	while (c != 'q');

	System.out.println("");
    }

    /**
     * Returns the current process.
     *
     * @return	the current process, or <tt>null</tt> if no process is current.
     */
    public static UserProcess currentProcess() {
	if (!(KThread.currentThread() instanceof UThread))
	    return null;
	
	return ((UThread) KThread.currentThread()).process;
    }

    /**
     * The exception handler. This handler is called by the processor whenever
     * a user instruction causes a processor exception.
     *
     * <p>
     * When the exception handler is invoked, interrupts are enabled, and the
     * processor's cause register contains an integer identifying the cause of
     * the exception (see the <tt>exceptionZZZ</tt> constants in the
     * <tt>Processor</tt> class). If the exception involves a bad virtual
     * address (e.g. page fault, TLB miss, read-only, bus error, or address
     * error), the processor's BadVAddr register identifies the virtual address
     * that caused the exception.
     */
    public void exceptionHandler() {
	Lib.assertTrue(KThread.currentThread() instanceof UThread);

	UserProcess process = ((UThread) KThread.currentThread()).process;
	int cause = Machine.processor().readRegister(Processor.regCause);
	process.handleException(cause);
    }

    /**
     * Start running user programs, by creating a process and running a shell
     * program in it. The name of the shell program it must run is returned by
     * <tt>Machine.getShellProgramName()</tt>.
     *
     * @see	nachos.machine.Machine#getShellProgramName
     */
    public void run() {
	super.run();

	UserProcess process = UserProcess.newUserProcess();
	
	String shellProgram = Machine.getShellProgramName();	
	Lib.assertTrue(process.execute(shellProgram, new String[] { }));

	KThread.currentThread().finish();
    }

    /**
     * Terminate this kernel. Never returns.
     */
    public void terminate() {
	super.terminate();
    }
    
    /** 
     * Group 7 start
     * methods to get processPageTable and free memory
     */
    public static int getFreeMemorySize(){
    	int freeMem = 0;
    	lmutex.P();
    	freeMem =  freeMemory.size();
    	lmutex.V();
    	return freeMem;
    }
    
    public static int getFreePage(){
    	Lib.assertTrue(freeMemory.size() > 0, "Run out of memory");
    	int freePage = 0;
    	lmutex.P();
    	freePage = freeMemory.pop();
    	lmutex.V();
    	return freePage;
    }
    
    public static void returnPage(int page){
    	lmutex.P();
    	freeMemory.push(page);
    	lmutex.V();
    }
    
    public static void createProcessTable(int processID, int tableSize){
    	tmutex.P();
    	Lib.debug(dbgProcess, "Creating process " + processID + " table size: " + tableSize);
    	TranslationEntry[] pageTable = new TranslationEntry[tableSize];
    	String ppList = "";
    	int ppn = 0;
		for (int i = 0; i<tableSize; i++){
			ppn = (Integer) UserKernel.getFreePage();
			ppList = ppList + ", " + ppn;
			pageTable[i] = new TranslationEntry(i,ppn, true,false,false,false);
			
		}
		Lib.debug(dbgProcess, "Physical pages in the process: " + ppList);
    	
		processTableMap.put(processID, pageTable);
    	tmutex.V();
    }
    
    public static void destroyProcessTable(int processID){
    	tmutex.P();
    	processTableMap.remove(processID);
    	tmutex.V();
    }
    public static TranslationEntry[] getProcessPageTable(int processID){
    	TranslationEntry[] pageTable;
    	tmutex.P();
    	pageTable =  processTableMap.get(processID);
    	tmutex.V();
    	return pageTable;
    }
    /** Globally accessible reference to the synchronized console. */
    public static SynchConsole console;

    // dummy variables to make javac smarter
    private static Coff dummy1 = null;
    
    
    
    /**
     * Group 7 start
     * 
     */
    
    private static Stack<Integer> freeMemory;
    private static HashMap<Integer, TranslationEntry[]> processTableMap;
    private static Semaphore tmutex; 
    private static Semaphore lmutex; 
    private static final char dbgProcess = 'a';
    
    
    /**
     * Group 7 end
     * 
     */
}
